package uva;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;

import rice.p2p.commonapi.Application;
import rice.p2p.commonapi.Endpoint;
import rice.p2p.commonapi.Id;
import rice.p2p.commonapi.Message;
import rice.p2p.commonapi.Node;
import rice.p2p.commonapi.NodeHandle;
import rice.p2p.commonapi.RouteMessage;
import rice.p2p.commonapi.appsocket.AppSocket;
import rice.p2p.commonapi.appsocket.AppSocketReceiver;
import rice.pastry.PastryNode;
import rice.tutorial.appsocket.MyApp;
import rice.environment.logging.Logger;

/**
 * A very simple application.
 * 
 * @author Jeff Hoye
 */
public class PotentiallyMaliciousApp implements Application {
	/**
	 * The Endpoint represents the underlieing node. By making calls on the
	 * Endpoint, it assures that the message will be delivered to a MyApp on
	 * whichever node the message is intended for.
	 */
	protected Endpoint endpoint;
	private boolean isMalicious;

	ByteBuffer out;
	ByteBuffer in;


	/**
	 * The node we were constructed on.
	 */
	protected Node node;

	public PotentiallyMaliciousApp(Node node, boolean isMalicious) throws IOException {
		// We are only going to use one instance of this application on each
		// PastryNode
		this.endpoint = node.buildEndpoint(this, "myinstance");

		this.node = node;
		this.isMalicious = isMalicious;
		

		endpoint.accept(new AppSocketReceiver() {
			/**
			 * When we accept a new socket.
			 */
			public void receiveSocket(AppSocket socket) {

				// this code reuses "this" AppSocketReceiver, and registers for
				// reading only, and a timeout of 30000.
				socket.register(true, false, 30000, this);

				// it's critical to call this to be able to accept multiple
				// times
				endpoint.accept(this);
			}

			/**
			 * Called when the socket is ready for reading or writing.
			 */
			public void receiveSelectResult(AppSocket socket, boolean canRead,
					boolean canWrite) {
				in = ByteBuffer.allocate(100000);
				try {
					// read from the socket into ins
					long remaining = socket.read(in);
					
					byte[] result = new byte[(int)remaining];
					for(int i = 0; i < remaining; i++){
						result[i] = in.array()[i];
					}

					ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(result);
					ObjectInput objectInput = new ObjectInputStream(byteArrayInputStream);
					DirectMessage message = (DirectMessage)objectInput.readObject(); 
					byteArrayInputStream.close();
					objectInput.close();

					
					
					System.out.println(PotentiallyMaliciousApp.this.node
							.getLocalNodeHandle()
							+ " Received message using direct socket " +message.getMessage() + " from " + message.getOrigin().getId().toStringFull());
				} catch (Exception ioe) {
					ioe.printStackTrace();
				}
				// only need to do this if expecting more messages
				// socket.register(true, false, 3000, this);
			}

			/**
			 * Called if we have a problem.
			 */
			public void receiveException(AppSocket socket, Exception e) {
				e.printStackTrace();
			}
		});

		// now we can receive messages
		this.endpoint.register();

		// System.out.println("registered endpoint: " + this.endpoint + " for "
		// + node.getId().toStringFull());
	}

	/**
	 * Called to directly send a message to the nh
	 * @throws IOException 
	 */
	public void sendMyMsgDirect(NodeHandle nh, String txt) throws IOException {
		System.out.println(this + " opening to " + nh);
		
		DirectMessage directMessage = new DirectMessage(node.getLocalNodeHandle(), txt);
		ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
		ObjectOutput objectOutput = new ObjectOutputStream(byteArrayOutputStream);   
		objectOutput.writeObject(directMessage);
		byte[] yourBytes = byteArrayOutputStream.toByteArray();
		System.out.println("bytes to be sent direct: " + yourBytes.length);
		out = ByteBuffer.wrap(yourBytes);
		
		objectOutput.close();
		byteArrayOutputStream.close();
		
		
		endpoint.connect(nh, new AppSocketReceiver() {

			/**
			 * Called when the socket comes available.
			 */
			public void receiveSocket(AppSocket socket) {
				// register for writing
				socket.register(false, true, 30000, this);
			}

			/**
			 * Called if there is a problem.
			 */
			public void receiveException(AppSocket socket, Exception e) {
				e.printStackTrace();
			}

			/**
			 * Example of how to write some bytes
			 */
			public void receiveSelectResult(AppSocket socket, boolean canRead,
					boolean canWrite) {
				try {
					long ret = socket.write(out);
					// see if we are done
					if (!out.hasRemaining()) {
						socket.close();
						out.clear();
					} else {
						// keep writing
						socket.register(false, true, 30000, this);
					}
				} catch (IOException ioe) {
					ioe.printStackTrace();
				}
			}
		}, 30000);
	}


	public Node getNode() {
		return node;
	}

	public void routeMessage(MyMsg message) {
		System.out.println(this.getNode().getId().toStringFull()
				+ " sending to " + message.from.toStringFull());
		endpoint.route(message.to, message, null);
	}

	public void routeMyMsgDirect(NodeHandle nh) {
		System.out.println(this + " sending direct to " + nh);
		Message msg = new MyMsg(endpoint.getId(), nh.getId());
		endpoint.route(null, msg, nh);
	}

	public void deliver(Id id, Message message) {
		System.out.println(this.getNode().getId().toStringFull() + " received "
				+ message);
	}

	public void update(NodeHandle handle, boolean joined) {
	}


	public boolean forward(RouteMessage message) {
		if (isMalicious) {
			System.out.println("malice is forwarding message: " + message
					+ " myid is " + this.getNode().getId().toStringFull()
					+ " and isMalicious is " + isMalicious);
			return true;
		} else {
			System.out.println("node is forwarding message: " + message
					+ " myid is " + this.getNode().getId().toStringFull());
			// System.out.println(((PastryNode)getNode()).getRoutingTable().printSelf());
			// System.out.println("leaf set: " +
			// ((PastryNode)getNode()).getLeafSet().toString());
		}
		return true;
	}

	public String toString() {
		return "MaliciousApp " + endpoint.getId();
	}

	public boolean getIsMalicious() {
		return isMalicious;
	}

	public void setMalicious(boolean isMalicious) {
		this.isMalicious = isMalicious;
	}


}
